#pragma once


#include "PlayerBase.cpp"

extern "C" {
#include <sys/socket.h>
#include <endian.h>
#include <arpa/inet.h>
#include <unistd.h>
}

class ServerInfo {
public:
    std::shared_ptr<char> tcpAddress;
    int tcpPort;
};

class TCPlayer : public PlayerBase {
public:
    TCPlayer(ServerInfo &&address) : tcpAddress{std::move(address)} {
        std::thread t{[this] {
            std::lock_guard<std::mutex> lockGuard{stateMutex};
            initContext();
        }};
        t.detach();
    }

    ~TCPlayer() {
        std::lock_guard<std::mutex> lockGuard{stateMutex};
        pendingState = VideoStateEnum::destroy;
        releaseContext();
    }

    void resumePlay() override {
        if (pendingState != VideoStateEnum::destroy)
            pendingState = VideoStateEnum::resume;
        std::thread t{[this] {
            std::lock_guard<std::mutex> lockGuard{stateMutex};
            if (pendingState != VideoStateEnum::resume ||
                videoState == VideoStateEnum::resuming ||
                videoState == VideoStateEnum::resume) {
                return;
            }
            setState(VideoStateEnum::resuming);
            LOGD("ggg %d resume start", tcpAddress.tcpPort);
            //连接Socket
            socketId = connectRemote();
            if (socketId == 0) {
                LOGE("ggg %d resume socket", tcpAddress.tcpPort);
                releaseContext();
                return;
            }
            errCount.store(0);
            //初始化失败了释放资源
            if (!initContext()) {
                LOGE("ggg %d resume initContext error", tcpAddress.tcpPort);
                releaseContext();
                return;
            }
            //nativeWindow资源准备
            if (gPSurface != nullptr) {
                JniEnv env;
                while (pNativeWindow == nullptr || vHeight <= 0 || vWidth <= 0) {
                    if (errCount.load() > retryCount) {
                        LOGE("ggg %d resume NativeWindow", tcpAddress.tcpPort);
                        releaseContext();
                        return;
                    }
                    if (errCount.load() > 0) std::this_thread::sleep_for(64ms);
                    pNativeWindow = ANativeWindow_fromSurface(*env, gPSurface);
                    if (pNativeWindow != nullptr) {
                        vHeight = ANativeWindow_getHeight(pNativeWindow);
                        vWidth = ANativeWindow_getWidth(pNativeWindow);
                    }
                    errCount++;
                }
                errCount.store(0);
            } else {
                LOGE("ggg %d resume Surface", tcpAddress.tcpPort);
                releaseContext();
                return;
            }

            ANativeWindow_setBuffersGeometry(pNativeWindow, vWidth, vHeight,
                                             WINDOW_FORMAT_RGBA_8888);

            //清空之前的队列
            qFrameRGBA.clean();
            qFrame.clean();

            //开始循环
            loop.resume();
            setState(VideoStateEnum::resume);
            LOGD("ggg %d resume end", tcpAddress.tcpPort);
        }};
        t.detach();
    }

    void pausePlay() override {
        if (pendingState != VideoStateEnum::destroy && videoState != VideoStateEnum::destroy)
            pendingState = VideoStateEnum::pause;
        std::thread t{[this] {
            std::lock_guard<std::mutex> lockGuard{stateMutex};
            if (pendingState != VideoStateEnum::pause ||
                videoState == VideoStateEnum::destroy ||
                videoState == VideoStateEnum::pausing ||
                videoState == VideoStateEnum::pause) {
                return;
            }
            setState(VideoStateEnum::pausing);
            LOGD("ggg %d pause start", tcpAddress.tcpPort);
            loop.pause();
            disConnect();
            ANativeWindow_release(pNativeWindow);
            pNativeWindow = nullptr;
            vHeight = -1;
            vWidth = -1;
            setState(VideoStateEnum::pause);
            LOGD("ggg %d pause end", tcpAddress.tcpPort);
        }};
        t.detach();
    }

protected:
    void read() override {
        LOGD("fff %d read start", tcpAddress.tcpPort);
        //尝试读取一个压缩帧
        unsigned char inBuffer[MAX_LEN];
        int curSize = recv(socketId, (char *) inBuffer, MAX_LEN, 0);
        if (curSize > 0) {
            unsigned char *curPtr{inBuffer};
            while (curSize > 0) {
                auto packet{std::make_shared<AVPacket>()};
                int len = av_parser_parse2(
                        pCodecParserCtx, pCodecCtx, &packet->data, &packet->size, curPtr, curSize,
                        AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
                curPtr += len;
                curSize -= len;
                if (packet->size != 0) {
                    errCount.store(0);
                    //尝试解码
                    int finished = -1;
                    auto frame{getFrame()};
                    avcodec_decode_video2(pCodecCtx, frame.get(), &finished, packet.get());
                    if (finished == 0) return;
                    qFrame.push(std::move(frame));
                    LOGI("fff %d read succeed", tcpAddress.tcpPort);
                }
            }
        } else if (errCount.load() >= retryCount) {
            LOGE("fff %d read releaseContext", tcpAddress.tcpPort);
            std::lock_guard<std::mutex> lockGuard{stateMutex};
            releaseContext();
        } else {
            errCount++;
            LOGE("fff %d read error %d", tcpAddress.tcpPort, errCount.load());
        }
    }

    void decode() override {
        LOGD("fff %d decode start", tcpAddress.tcpPort);
        //尝试获取一个压缩帧
        std::shared_ptr<AVFrame> frame;
        try {
            frame = qFrame.tryGetOne();
        } catch (...) {
            LOGE("fff %d decode empty queue", tcpAddress.tcpPort);
            return;
        }
        //格式转换
        auto frameRGBA{getFrame(true)};
        uint8_t *pBuffer = (uint8_t *)
                av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGBA, vWidth, vHeight, 1) *
                          sizeof(uint8_t));
        av_image_fill_arrays(frameRGBA->data, frameRGBA->linesize, pBuffer,
                             AV_PIX_FMT_RGBA, vWidth, vHeight, 1);
        SwsContext *pSws = sws_getContext(pCodecCtx->width, pCodecCtx->height,
                                          pCodecCtx->pix_fmt,
                                          vWidth, vHeight, AV_PIX_FMT_RGBA,
                                          SWS_FAST_BILINEAR, NULL, NULL, NULL);
        sws_scale(pSws, (uint8_t const *const *) frame->data, frame->linesize, 0,
                  pCodecCtx->height, frameRGBA->data, frameRGBA->linesize);
        sws_freeContext(pSws);
        //保存
        qFrameRGBA.push(std::move(frameRGBA));
        LOGD("fff %d decode succeed", tcpAddress.tcpPort);
    }

    void renderer() override {
        LOGD("fff %d rend start", tcpAddress.tcpPort);
        //尝试获取一个解码帧
        std::shared_ptr<AVFrame> frameRGBA;
        try {
            frameRGBA = qFrameRGBA.tryGetOne(true);
        } catch (...) {
            LOGE("fff %d rend empty queue", tcpAddress.tcpPort);
            return;
        }
        if (ANativeWindow_lock(pNativeWindow, &windowBuffer, 0) >= 0) {
            // 获取stride
            uint8_t *dst = (uint8_t *) windowBuffer.bits;
            int dstStride = windowBuffer.stride * 4;
            uint8_t *src = (frameRGBA->data[0]);
            size_t srcStride = (size_t) frameRGBA->linesize[0];
            // 由于window的stride和帧的stride不同,因此需要逐行复制
            for (int h = 0; h < vHeight; h++) {
                memcpy(dst + h * dstStride, src + h * srcStride, srcStride);
            }
            ANativeWindow_unlockAndPost(pNativeWindow);
        }
        LOGD("fff %d rend succeed", tcpAddress.tcpPort);
    }

    void releaseContext() override {
        LOGD("ccc %d release start", tcpAddress.tcpPort);
        loop.reset();
        pool.reset();
        qFrameRGBA.clean();
        qFrame.clean();
        if (pCodecCtx != nullptr) {
            avcodec_close(pCodecCtx);
            avcodec_free_context(&pCodecCtx);
        }
        if (pCodecParserCtx) {
            av_parser_close(pCodecParserCtx);
        }
        if (pNativeWindow != nullptr) {
            ANativeWindow_release(pNativeWindow);
        }

        pCodecCtx = nullptr;
        pCodecParserCtx = nullptr;
        pNativeWindow = nullptr;
        disConnect();
        setState(VideoStateEnum::destroy);
        LOGD("ccc %d release end", tcpAddress.tcpPort);
    }

    bool initContext() override {
        if (pCodecParserCtx != nullptr) return true;//初始化一次
        LOGD("ccc %d initContext start", tcpAddress.tcpPort);
        av_register_all();
        AVCodec *pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
        if (pCodec == nullptr) {
            LOGE("ccc %d avcodec_find_decoder", tcpAddress.tcpPort);
            return false;
        }

        pCodecCtx = avcodec_alloc_context3(pCodec);
        if (pCodecCtx == nullptr) {
            LOGE("ccc %d avcodec_find_decoder", tcpAddress.tcpPort);
            return false;
        }

        pCodecParserCtx = av_parser_init(AV_CODEC_ID_H264);
        if (pCodecParserCtx == nullptr) {
            LOGE("ccc %d av_parser_init", tcpAddress.tcpPort);
            return 0;
        }

        if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
            LOGE("ccc %d avcodec_open2", tcpAddress.tcpPort);
            return 0;
        }

        loop.pause();

        pool.commit(loop([this] {
            renderer();
        }));

        pool.commit(loop([this] {
            decode();
        }));

        pool.commit(loop([this] {
            read();
        }));
        LOGD("ccc %d initContext end", tcpAddress.tcpPort);
        return true;
    }

private:
    static constexpr size_t MAX_LEN{10 * 1024};
    int vHeight{0};
    int vWidth{0};
    ServerInfo tcpAddress;
    int socketId{0};
    ThreadQueue<std::shared_ptr<AVFrame>> qFrame;//解码完毕后队列
    ThreadQueue<std::shared_ptr<AVFrame>> qFrameRGBA;//格式转换后的队列
    ANativeWindow *pNativeWindow{nullptr};
    AVCodecContext *pCodecCtx{nullptr};
    AVCodecParserContext *pCodecParserCtx{nullptr};
    ANativeWindow_Buffer windowBuffer;


    int sdwOptVal = 500 * 1024;

    int connectRemote() {
        int s = socket(AF_INET, SOCK_STREAM, 0);
        if (s < 0) {
            LOGE("ccc %d s:%d", tcpAddress.tcpPort, s);
            return 0;
        }
        if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *) &sdwOptVal, sizeof(sdwOptVal)) == -1) {
            LOGE("ccc %d setsockopt code:%d", tcpAddress.tcpPort, s);
            return 0;
        }
        struct sockaddr_in serverAddress{AF_INET, htons(tcpAddress.tcpPort)};

        if (inet_pton(AF_INET, tcpAddress.tcpAddress.get(), &serverAddress.sin_addr) < 0) {
            LOGE("ccc %d inet_pton", tcpAddress.tcpPort);
            return 0;
        }

        struct timeval timeout = {1, 0};
        //设置发送超时
        setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout, sizeof(struct timeval));
        //设置接收超时
        setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));

        int connectFd = connect(s, (struct sockaddr *) &serverAddress, sizeof(serverAddress));
        if (connectFd < 0) {
            LOGE("ccc %d connect code:%d", tcpAddress.tcpPort, connectFd);
            return 0;
        }
        return s;
    }

    void disConnect() {
        if (socketId == 0) return;
        close(socketId);
        socketId = 0;
    }
};